package com.leyou.gateway.filter;

import com.leyou.common.auth.pojo.Payload;
import com.leyou.common.auth.pojo.UserInfo;
import com.leyou.common.auth.utils.JwtUtils;
import com.leyou.common.constants.RedisConstants;
import com.leyou.common.constants.UserTokenConstants;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.CookieUtils;
import com.leyou.gateway.config.FilterProperties;
import com.leyou.gateway.config.JwtProperties;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;

/**
 * @author 虎哥
 */
@Slf4j
@Component
public class LoginFilter extends ZuulFilter {

    private final JwtProperties prop;
    private final FilterProperties filterProp;
    private final StringRedisTemplate redisTemplate;

    public LoginFilter(JwtProperties prop, FilterProperties filterProp, StringRedisTemplate redisTemplate) {
        this.prop = prop;
        this.filterProp = filterProp;
        this.redisTemplate = redisTemplate;
    }

    /**
     * 过滤器的类型，包括四种：pre、route、post、error
     */
    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    /**
     * 当过滤器类型一样时，执行的顺序有int值决定，值越小优先级越高
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * 是否要执行run方法
     *
     * @return true:执行run方法；false：不执行run方法
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 拦截器的业务逻辑
     */
    @Override
    public Object run() throws ZuulException {
        // 1.获取Request
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        // 2.获取cookie
        String token = CookieUtils.getCookieValue(request, UserTokenConstants.COOKIE_NAME);
        try {
            // 3.校验cookie中的token的有效性
            Payload<UserInfo> payload = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), UserInfo.class);
            // 4.获取token中的用户信息
            UserInfo userInfo = payload.getUserInfo();
            // 5.校验token是否与redis中一致
            // 5.1.取出redis中的JTI
            String cacheJTI = redisTemplate.opsForValue().get(RedisConstants.AUTH_LOGIN_KEY_PREFIX + userInfo.getId());
            // 5.2.与token中的JTI比较
            if (!StringUtils.equals(cacheJTI, payload.getId())) {
                // tokenID与redis中的不一致，应该是无效的token
                throw new LyException(401, "未登录或登录超时");
            }

            // 6.刷新token
            redisTemplate.expire(RedisConstants.AUTH_LOGIN_KEY_PREFIX + userInfo.getId(),
                    RedisConstants.TOKEN_EXPIRE_MINUTES, TimeUnit.MINUTES);
            // TODO 6.权限控制（可选）

            // 7.放行请求
            log.info("{}正在访问资源: {}", userInfo.getUsername(), request.getRequestURI());
        } catch (Exception e) {
            // 白名单判断，如果不在白名单，
            if(!isAllow(request)) {
                // 校验失败，说明登录状态失效，返回401
                context.setResponseStatusCode(401);
                // 拦截请求
                context.setSendZuulResponse(false);
            }
        }
        return null;
    }

    private boolean isAllow(HttpServletRequest request){
        // 获取当前请求路径
        String path = request.getRequestURI();
        // 获取当前请求method
        String method = request.getMethod();
        // 获取许可的请求白名单
        Map<String, String> allowPaths = filterProp.getAllowPaths();
        for (Map.Entry<String, String> entry : allowPaths.entrySet()) {
            // 获取许可的路径和方式
            String allowPath = entry.getKey();
            String allowMethod = entry.getValue();
            // 判断是否许可
            if (StringUtils.startsWith(path, allowPath) && StringUtils.equals(method, allowMethod)) {
                // 当前请求路径在白名单中，应该放行
                return true;
            }
        }
        // 不应该放行，我们返回false
        return false;
    }
}
